use std::{pin::Pin};
use std::task::{Context, Poll};


use actix_service::{Service,Transform};
use actix_session::{ UserSession};
use actix_web::{dev::{ServiceRequest,ServiceResponse},Error};
use futures::future::{ok,err,Ready};
use futures::Future;
use crate::dao::{
    services::user_service::find_user_role,
    domain::user::UserRoleEnum
};
use crate::prelude::*;

pub struct Auth {
    role:i32,
    pass:bool
}
impl Default for Auth {
    fn default() -> Self {
        Auth {
            role:1,
            pass:false
        }
    }
}


impl<S,B> Transform<S> for Auth 
where 
    S:Service<Request=ServiceRequest,Response=ServiceResponse<B>,Error=Error>,
    S::Future:'static,
    B:'static
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = AuthMiddleware<S>;
    type Future = Ready<Result<Self::Transform,Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(AuthMiddleware{service,role:self.role,pass:self.pass})
    }
}

pub struct AuthMiddleware<S> {
    service:S,
    role:i32,
    pass:bool,
}

impl<S,B> Service for AuthMiddleware<S> 
where 
    S:Service<Request=ServiceRequest,Response=ServiceResponse<B>,Error=Error>,
    S::Future:'static,
    B:'static

{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Pin<Box<dyn Future<Output=Result<Self::Response,Self::Error>>>>;

    fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.service.poll_ready(ctx)
    }

    fn call(&mut self,  req: Self::Request) -> Self::Future {
        let session = req.get_session();
        let role = self.role;
        let user_id:Option<i64> = {
            if self.pass {
                None
            } else {
                let get_val = session.get("user_id");
                if let Err(e) = get_val {
                    if self.role & UserRoleEnum::Anon as i32 !=0 {
                        return Box::pin(err(e));
                    }
                    None
                } else {
                    get_val.ok().flatten()
                }
            }
        };
        let fut = self.service.call(req);
        
        
        Box::pin(async move {
            if let Some(user_id) = user_id {
                let v = find_user_role(user_id, crate::get_db_pool!()).await?;
                if v & role != role {
                    Err(AppError::Forbidden("promision denined".into()))?;
                }
            }
            let res = fut.await?;
            Ok(res)
        })
    }
}